//     __ _____ _____ _____
//  __|  |   __|     |   | |  JSON for Modern C++
// |  |  |__   |  |  | | | |  version 3.11.3
// |_____|_____|_____|_|___|  https://github.com/nlohmann/json
//
// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann <https://nlohmann.me>
// SPDX-License-Identifier: MIT

#pragma once

#include <algorithm>     // transform
#include <array>         // array
#include <forward_list>  // forward_list
#include <iterator>      // inserter, front_inserter, end
#include <map>           // map
#include <nlohmann/detail/exceptions.hpp>
#include <nlohmann/detail/macro_scope.hpp>
#include <nlohmann/detail/meta/cpp_future.hpp>
#include <nlohmann/detail/meta/identity_tag.hpp>
#include <nlohmann/detail/meta/std_fs.hpp>
#include <nlohmann/detail/meta/type_traits.hpp>
#include <nlohmann/detail/string_concat.hpp>
#include <nlohmann/detail/value_t.hpp>
#include <string>  // string
#include <tuple>   // tuple, make_tuple
#include <type_traits>  // is_arithmetic, is_same, is_enum, underlying_type, is_convertible
#include <unordered_map>  // unordered_map
#include <utility>        // pair, declval
#include <valarray>       // valarray

NLOHMANN_JSON_NAMESPACE_BEGIN
namespace detail {

template <typename BasicJsonType>
inline void from_json(const BasicJsonType &j, typename std::nullptr_t &n) {
  if (JSON_HEDLEY_UNLIKELY(!j.is_null())) {
    JSON_THROW(type_error::create(
        302, concat("type must be null, but is ", j.type_name()), &j));
  }
  n = nullptr;
}

// overloads for basic_json template parameters
template <
    typename BasicJsonType, typename ArithmeticType,
    enable_if_t<std::is_arithmetic<ArithmeticType>::value &&
                    !std::is_same<ArithmeticType,
                                  typename BasicJsonType::boolean_t>::value,
                int> = 0>
void get_arithmetic_value(const BasicJsonType &j, ArithmeticType &val) {
  switch (static_cast<value_t>(j)) {
  case value_t::number_unsigned: {
    val = static_cast<ArithmeticType>(
        *j.template get_ptr<
            const typename BasicJsonType::number_unsigned_t *>());
    break;
  }
  case value_t::number_integer: {
    val = static_cast<ArithmeticType>(
        *j.template get_ptr<
            const typename BasicJsonType::number_integer_t *>());
    break;
  }
  case value_t::number_float: {
    val = static_cast<ArithmeticType>(
        *j.template get_ptr<const typename BasicJsonType::number_float_t *>());
    break;
  }

  case value_t::null:
  case value_t::object:
  case value_t::array:
  case value_t::string:
  case value_t::boolean:
  case value_t::binary:
  case value_t::discarded:
  default:
    JSON_THROW(type_error::create(
        302, concat("type must be number, but is ", j.type_name()), &j));
  }
}

template <typename BasicJsonType>
inline void from_json(const BasicJsonType &j,
                      typename BasicJsonType::boolean_t &b) {
  if (JSON_HEDLEY_UNLIKELY(!j.is_boolean())) {
    JSON_THROW(type_error::create(
        302, concat("type must be boolean, but is ", j.type_name()), &j));
  }
  b = *j.template get_ptr<const typename BasicJsonType::boolean_t *>();
}

template <typename BasicJsonType>
inline void from_json(const BasicJsonType &j,
                      typename BasicJsonType::string_t &s) {
  if (JSON_HEDLEY_UNLIKELY(!j.is_string())) {
    JSON_THROW(type_error::create(
        302, concat("type must be string, but is ", j.type_name()), &j));
  }
  s = *j.template get_ptr<const typename BasicJsonType::string_t *>();
}

template <
    typename BasicJsonType, typename StringType,
    enable_if_t<
        std::is_assignable<StringType &,
                           const typename BasicJsonType::string_t>::value &&
            is_detected_exact<typename BasicJsonType::string_t::value_type,
                              value_type_t, StringType>::value &&
            !std::is_same<typename BasicJsonType::string_t,
                          StringType>::value &&
            !is_json_ref<StringType>::value,
        int> = 0>
inline void from_json(const BasicJsonType &j, StringType &s) {
  if (JSON_HEDLEY_UNLIKELY(!j.is_string())) {
    JSON_THROW(type_error::create(
        302, concat("type must be string, but is ", j.type_name()), &j));
  }

  s = *j.template get_ptr<const typename BasicJsonType::string_t *>();
}

template <typename BasicJsonType>
inline void from_json(const BasicJsonType &j,
                      typename BasicJsonType::number_float_t &val) {
  get_arithmetic_value(j, val);
}

template <typename BasicJsonType>
inline void from_json(const BasicJsonType &j,
                      typename BasicJsonType::number_unsigned_t &val) {
  get_arithmetic_value(j, val);
}

template <typename BasicJsonType>
inline void from_json(const BasicJsonType &j,
                      typename BasicJsonType::number_integer_t &val) {
  get_arithmetic_value(j, val);
}

#if !JSON_DISABLE_ENUM_SERIALIZATION
template <typename BasicJsonType, typename EnumType,
          enable_if_t<std::is_enum<EnumType>::value, int> = 0>
inline void from_json(const BasicJsonType &j, EnumType &e) {
  typename std::underlying_type<EnumType>::type val;
  get_arithmetic_value(j, val);
  e = static_cast<EnumType>(val);
}
#endif  // JSON_DISABLE_ENUM_SERIALIZATION

// forward_list doesn't have an insert method
template <typename BasicJsonType, typename T, typename Allocator,
          enable_if_t<is_getable<BasicJsonType, T>::value, int> = 0>
inline void from_json(const BasicJsonType &j,
                      std::forward_list<T, Allocator> &l) {
  if (JSON_HEDLEY_UNLIKELY(!j.is_array())) {
    JSON_THROW(type_error::create(
        302, concat("type must be array, but is ", j.type_name()), &j));
  }
  l.clear();
  std::transform(j.rbegin(), j.rend(), std::front_inserter(l),
                 [](const BasicJsonType &i) { return i.template get<T>(); });
}

// valarray doesn't have an insert method
template <typename BasicJsonType, typename T,
          enable_if_t<is_getable<BasicJsonType, T>::value, int> = 0>
inline void from_json(const BasicJsonType &j, std::valarray<T> &l) {
  if (JSON_HEDLEY_UNLIKELY(!j.is_array())) {
    JSON_THROW(type_error::create(
        302, concat("type must be array, but is ", j.type_name()), &j));
  }
  l.resize(j.size());
  std::transform(
      j.begin(), j.end(), std::begin(l),
      [](const BasicJsonType &elem) { return elem.template get<T>(); });
}

template <typename BasicJsonType, typename T, std::size_t N>
auto from_json(
    const BasicJsonType &j,
    T (&arr)
        [N])  // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)
    -> decltype(j.template get<T>(), void()) {
  for (std::size_t i = 0; i < N; ++i) {
    arr[i] = j.at(i).template get<T>();
  }
}

template <typename BasicJsonType>
inline void from_json_array_impl(const BasicJsonType &j,
                                 typename BasicJsonType::array_t &arr,
                                 priority_tag<3> /*unused*/) {
  arr = *j.template get_ptr<const typename BasicJsonType::array_t *>();
}

template <typename BasicJsonType, typename T, std::size_t N>
auto from_json_array_impl(const BasicJsonType &j, std::array<T, N> &arr,
                          priority_tag<2> /*unused*/)
    -> decltype(j.template get<T>(), void()) {
  for (std::size_t i = 0; i < N; ++i) {
    arr[i] = j.at(i).template get<T>();
  }
}

template <typename BasicJsonType, typename ConstructibleArrayType,
          enable_if_t<std::is_assignable<ConstructibleArrayType &,
                                         ConstructibleArrayType>::value,
                      int> = 0>
auto from_json_array_impl(const BasicJsonType &j, ConstructibleArrayType &arr,
                          priority_tag<1> /*unused*/)
    -> decltype(arr.reserve(
                    std::declval<typename ConstructibleArrayType::size_type>()),
                j.template get<typename ConstructibleArrayType::value_type>(),
                void()) {
  using std::end;

  ConstructibleArrayType ret;
  ret.reserve(j.size());
  std::transform(
      j.begin(), j.end(), std::inserter(ret, end(ret)),
      [](const BasicJsonType &i) {
        // get<BasicJsonType>() returns *this, this won't call a from_json
        // method when value_type is BasicJsonType
        return i.template get<typename ConstructibleArrayType::value_type>();
      });
  arr = std::move(ret);
}

template <typename BasicJsonType, typename ConstructibleArrayType,
          enable_if_t<std::is_assignable<ConstructibleArrayType &,
                                         ConstructibleArrayType>::value,
                      int> = 0>
inline void from_json_array_impl(const BasicJsonType &j,
                                 ConstructibleArrayType &arr,
                                 priority_tag<0> /*unused*/) {
  using std::end;

  ConstructibleArrayType ret;
  std::transform(
      j.begin(), j.end(), std::inserter(ret, end(ret)),
      [](const BasicJsonType &i) {
        // get<BasicJsonType>() returns *this, this won't call a from_json
        // method when value_type is BasicJsonType
        return i.template get<typename ConstructibleArrayType::value_type>();
      });
  arr = std::move(ret);
}

template <
    typename BasicJsonType, typename ConstructibleArrayType,
    enable_if_t<is_constructible_array_type<BasicJsonType,
                                            ConstructibleArrayType>::value &&
                    !is_constructible_object_type<
                        BasicJsonType, ConstructibleArrayType>::value &&
                    !is_constructible_string_type<
                        BasicJsonType, ConstructibleArrayType>::value &&
                    !std::is_same<ConstructibleArrayType,
                                  typename BasicJsonType::binary_t>::value &&
                    !is_basic_json<ConstructibleArrayType>::value,
                int> = 0>
auto from_json(const BasicJsonType &j, ConstructibleArrayType &arr)
    -> decltype(from_json_array_impl(j, arr, priority_tag<3>{}),
                j.template get<typename ConstructibleArrayType::value_type>(),
                void()) {
  if (JSON_HEDLEY_UNLIKELY(!j.is_array())) {
    JSON_THROW(type_error::create(
        302, concat("type must be array, but is ", j.type_name()), &j));
  }

  from_json_array_impl(j, arr, priority_tag<3>{});
}

template <typename BasicJsonType, typename T, std::size_t... Idx>
std::array<T, sizeof...(Idx)> from_json_inplace_array_impl(
    BasicJsonType &&j, identity_tag<std::array<T, sizeof...(Idx)>> /*unused*/,
    index_sequence<Idx...> /*unused*/) {
  return {{std::forward<BasicJsonType>(j).at(Idx).template get<T>()...}};
}

template <typename BasicJsonType, typename T, std::size_t N>
auto from_json(BasicJsonType &&j, identity_tag<std::array<T, N>> tag)
    -> decltype(from_json_inplace_array_impl(std::forward<BasicJsonType>(j),
                                             tag, make_index_sequence<N>{})) {
  if (JSON_HEDLEY_UNLIKELY(!j.is_array())) {
    JSON_THROW(type_error::create(
        302, concat("type must be array, but is ", j.type_name()), &j));
  }

  return from_json_inplace_array_impl(std::forward<BasicJsonType>(j), tag,
                                      make_index_sequence<N>{});
}

template <typename BasicJsonType>
inline void from_json(const BasicJsonType &j,
                      typename BasicJsonType::binary_t &bin) {
  if (JSON_HEDLEY_UNLIKELY(!j.is_binary())) {
    JSON_THROW(type_error::create(
        302, concat("type must be binary, but is ", j.type_name()), &j));
  }

  bin = *j.template get_ptr<const typename BasicJsonType::binary_t *>();
}

template <typename BasicJsonType, typename ConstructibleObjectType,
          enable_if_t<is_constructible_object_type<
                          BasicJsonType, ConstructibleObjectType>::value,
                      int> = 0>
inline void from_json(const BasicJsonType &j, ConstructibleObjectType &obj) {
  if (JSON_HEDLEY_UNLIKELY(!j.is_object())) {
    JSON_THROW(type_error::create(
        302, concat("type must be object, but is ", j.type_name()), &j));
  }

  ConstructibleObjectType ret;
  const auto *inner_object =
      j.template get_ptr<const typename BasicJsonType::object_t *>();
  using value_type = typename ConstructibleObjectType::value_type;
  std::transform(
      inner_object->begin(), inner_object->end(),
      std::inserter(ret, ret.begin()),
      [](typename BasicJsonType::object_t::value_type const &p) {
        return value_type(
            p.first,
            p.second
                .template get<typename ConstructibleObjectType::mapped_type>());
      });
  obj = std::move(ret);
}

// overload for arithmetic types, not chosen for basic_json template arguments
// (BooleanType, etc..); note: Is it really necessary to provide explicit
// overloads for boolean_t etc. in case of a custom BooleanType which is not
// an arithmetic type?
template <
    typename BasicJsonType, typename ArithmeticType,
    enable_if_t<
        std::is_arithmetic<ArithmeticType>::value &&
            !std::is_same<ArithmeticType,
                          typename BasicJsonType::number_unsigned_t>::value &&
            !std::is_same<ArithmeticType,
                          typename BasicJsonType::number_integer_t>::value &&
            !std::is_same<ArithmeticType,
                          typename BasicJsonType::number_float_t>::value &&
            !std::is_same<ArithmeticType,
                          typename BasicJsonType::boolean_t>::value,
        int> = 0>
inline void from_json(const BasicJsonType &j, ArithmeticType &val) {
  switch (static_cast<value_t>(j)) {
  case value_t::number_unsigned: {
    val = static_cast<ArithmeticType>(
        *j.template get_ptr<
            const typename BasicJsonType::number_unsigned_t *>());
    break;
  }
  case value_t::number_integer: {
    val = static_cast<ArithmeticType>(
        *j.template get_ptr<
            const typename BasicJsonType::number_integer_t *>());
    break;
  }
  case value_t::number_float: {
    val = static_cast<ArithmeticType>(
        *j.template get_ptr<const typename BasicJsonType::number_float_t *>());
    break;
  }
  case value_t::boolean: {
    val = static_cast<ArithmeticType>(
        *j.template get_ptr<const typename BasicJsonType::boolean_t *>());
    break;
  }

  case value_t::null:
  case value_t::object:
  case value_t::array:
  case value_t::string:
  case value_t::binary:
  case value_t::discarded:
  default:
    JSON_THROW(type_error::create(
        302, concat("type must be number, but is ", j.type_name()), &j));
  }
}

template <typename BasicJsonType, typename... Args, std::size_t... Idx>
std::tuple<Args...> from_json_tuple_impl_base(
    BasicJsonType &&j, index_sequence<Idx...> /*unused*/) {
  return std::make_tuple(
      std::forward<BasicJsonType>(j).at(Idx).template get<Args>()...);
}

template <typename BasicJsonType, class A1, class A2>
std::pair<A1, A2> from_json_tuple_impl(
    BasicJsonType &&j, identity_tag<std::pair<A1, A2>> /*unused*/,
    priority_tag<0> /*unused*/) {
  return {std::forward<BasicJsonType>(j).at(0).template get<A1>(),
          std::forward<BasicJsonType>(j).at(1).template get<A2>()};
}

template <typename BasicJsonType, typename A1, typename A2>
inline void from_json_tuple_impl(BasicJsonType &&j, std::pair<A1, A2> &p,
                                 priority_tag<1> /*unused*/) {
  p = from_json_tuple_impl(std::forward<BasicJsonType>(j),
                           identity_tag<std::pair<A1, A2>>{},
                           priority_tag<0>{});
}

template <typename BasicJsonType, typename... Args>
std::tuple<Args...> from_json_tuple_impl(
    BasicJsonType &&j, identity_tag<std::tuple<Args...>> /*unused*/,
    priority_tag<2> /*unused*/) {
  return from_json_tuple_impl_base<BasicJsonType, Args...>(
      std::forward<BasicJsonType>(j), index_sequence_for<Args...>{});
}

template <typename BasicJsonType, typename... Args>
inline void from_json_tuple_impl(BasicJsonType &&j, std::tuple<Args...> &t,
                                 priority_tag<3> /*unused*/) {
  t = from_json_tuple_impl_base<BasicJsonType, Args...>(
      std::forward<BasicJsonType>(j), index_sequence_for<Args...>{});
}

template <typename BasicJsonType, typename TupleRelated>
auto from_json(BasicJsonType &&j, TupleRelated &&t)
    -> decltype(from_json_tuple_impl(std::forward<BasicJsonType>(j),
                                     std::forward<TupleRelated>(t),
                                     priority_tag<3>{})) {
  if (JSON_HEDLEY_UNLIKELY(!j.is_array())) {
    JSON_THROW(type_error::create(
        302, concat("type must be array, but is ", j.type_name()), &j));
  }

  return from_json_tuple_impl(std::forward<BasicJsonType>(j),
                              std::forward<TupleRelated>(t), priority_tag<3>{});
}

template <typename BasicJsonType, typename Key, typename Value,
          typename Compare, typename Allocator,
          typename = enable_if_t<!std::is_constructible<
              typename BasicJsonType::string_t, Key>::value>>
inline void from_json(const BasicJsonType &j,
                      std::map<Key, Value, Compare, Allocator> &m) {
  if (JSON_HEDLEY_UNLIKELY(!j.is_array())) {
    JSON_THROW(type_error::create(
        302, concat("type must be array, but is ", j.type_name()), &j));
  }
  m.clear();
  for (const auto &p : j) {
    if (JSON_HEDLEY_UNLIKELY(!p.is_array())) {
      JSON_THROW(type_error::create(
          302, concat("type must be array, but is ", p.type_name()), &j));
    }
    m.emplace(p.at(0).template get<Key>(), p.at(1).template get<Value>());
  }
}

template <typename BasicJsonType, typename Key, typename Value, typename Hash,
          typename KeyEqual, typename Allocator,
          typename = enable_if_t<!std::is_constructible<
              typename BasicJsonType::string_t, Key>::value>>
inline void from_json(
    const BasicJsonType &j,
    std::unordered_map<Key, Value, Hash, KeyEqual, Allocator> &m) {
  if (JSON_HEDLEY_UNLIKELY(!j.is_array())) {
    JSON_THROW(type_error::create(
        302, concat("type must be array, but is ", j.type_name()), &j));
  }
  m.clear();
  for (const auto &p : j) {
    if (JSON_HEDLEY_UNLIKELY(!p.is_array())) {
      JSON_THROW(type_error::create(
          302, concat("type must be array, but is ", p.type_name()), &j));
    }
    m.emplace(p.at(0).template get<Key>(), p.at(1).template get<Value>());
  }
}

#if JSON_HAS_FILESYSTEM || JSON_HAS_EXPERIMENTAL_FILESYSTEM
template <typename BasicJsonType>
inline void from_json(const BasicJsonType &j, std_fs::path &p) {
  if (JSON_HEDLEY_UNLIKELY(!j.is_string())) {
    JSON_THROW(type_error::create(
        302, concat("type must be string, but is ", j.type_name()), &j));
  }
  p = *j.template get_ptr<const typename BasicJsonType::string_t *>();
}
#endif

struct from_json_fn {
  template <typename BasicJsonType, typename T>
  auto operator()(const BasicJsonType &j, T &&val) const
      noexcept(noexcept(from_json(j, std::forward<T>(val))))
          -> decltype(from_json(j, std::forward<T>(val))) {
    return from_json(j, std::forward<T>(val));
  }
};

}  // namespace detail

#ifndef JSON_HAS_CPP_17
/// namespace to hold default `from_json` function
/// to see why this is required:
/// http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4381.html
namespace  // NOLINT(cert-dcl59-cpp,fuchsia-header-anon-namespaces,google-build-namespaces)
{
#endif
JSON_INLINE_VARIABLE constexpr const auto
    &from_json =  // NOLINT(misc-definitions-in-headers)
    detail::static_const<detail::from_json_fn>::value;
#ifndef JSON_HAS_CPP_17
}  // namespace
#endif

NLOHMANN_JSON_NAMESPACE_END
